package com.yxj.gm;

import com.kms.jca.UseKey;
import com.kms.provider.keyPair.ZyxxPrivateKey;
import com.kms.provider.keyPair.ZyxxPublicKey;

import java.io.File;
import java.math.BigInteger;
import java.security.KeyPair;

public class SM2 {
    static byte[] p = new byte[]{(byte) 0x0,(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
    static byte[] a = new byte[]{(byte) 0x0,(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC};

    static byte[] XG = new byte[]{(byte) 0x0,(byte) 0x32, (byte) 0xC4, (byte) 0xAE, (byte) 0x2C, (byte) 0x1F, (byte) 0x19, (byte) 0x81, (byte) 0x19, (byte) 0x5F, (byte) 0x99, (byte) 0x04, (byte) 0x46, (byte) 0x6A, (byte) 0x39, (byte) 0xC9, (byte) 0x94, (byte) 0x8F, (byte) 0xE3, (byte) 0x0B, (byte) 0xBF, (byte) 0xF2, (byte) 0x66, (byte) 0x0B, (byte) 0xE1, (byte) 0x71, (byte) 0x5A, (byte) 0x45, (byte) 0x89, (byte) 0x33, (byte) 0x4C, (byte) 0x74, (byte) 0xC7};

    static byte[] YG = new byte[]{(byte) 0x0,(byte) 0xBC, (byte) 0x37, (byte) 0x36, (byte) 0xA2, (byte) 0xF4, (byte) 0xF6, (byte) 0x77, (byte) 0x9C, (byte) 0x59, (byte) 0xBD, (byte) 0xCE, (byte) 0xE3, (byte) 0x6B, (byte) 0x69, (byte) 0x21, (byte) 0x53, (byte) 0xD0, (byte) 0xA9, (byte) 0x87, (byte) 0x7C, (byte) 0xC6, (byte) 0x2A, (byte) 0x47, (byte) 0x40, (byte) 0x02, (byte) 0xDF, (byte) 0x32, (byte) 0xE5, (byte) 0x21, (byte) 0x39, (byte) 0xF0, (byte) 0xA0};

    static byte[] n = new byte[]{(byte) 0x0,(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x72, (byte) 0x03, (byte) 0xDF, (byte) 0x6B, (byte) 0x21, (byte) 0xC6, (byte) 0x05, (byte) 0x2B, (byte) 0x53, (byte) 0xBB, (byte) 0xF4, (byte) 0x09, (byte) 0x39, (byte) 0xD5, (byte) 0x41, (byte) 0x23};
    static byte[] b = new byte[]{(byte) 0x0,(byte) 0x28, (byte) 0xE9, (byte) 0xFA, (byte) 0x9E, (byte) 0x9D, (byte) 0x9F, (byte) 0x5E, (byte) 0x34, (byte) 0x4D, (byte) 0x5A, (byte) 0x9E, (byte) 0x4B, (byte) 0xCF, (byte) 0x65, (byte) 0x09, (byte) 0xA7, (byte) 0xF3, (byte) 0x97, (byte) 0x89, (byte) 0xF5, (byte) 0x15, (byte) 0xAB, (byte) 0x8F, (byte) 0x92, (byte) 0xDD, (byte) 0xBC, (byte) 0xBD, (byte) 0x41, (byte) 0x4D, (byte) 0x94, (byte) 0x0E, (byte) 0x93};

    static int addCount=0,exGcdCount=0;

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            generateSM2KeyPair();
        }
        File file = new File("");

    }


    public static KeyPair generateSM2KeyPair(){

        //调用组件化生成32字节随机数
        UseKey useKey = new UseKey();
        byte[] random = useKey.secureRandom(32).getEncoded();
        //第一个字节补0
        //Biginteger会转换成有符号数造成精度丢失
        byte[] kt = new byte[33];
        System.arraycopy(random,0,kt,1,32);
        //倍点运算
        byte[][] bytes = MultiplePointOperation(XG, YG, kt, a, p);

        byte[] xt=bytes[0];
        byte[] yt=bytes[1];
        byte[] x=new byte[32],y=new byte[32],k=new byte[32];
        //计算结束后对计算结果进处理
        if(xt.length==33){
            //如果头部带有00则去出头部的00
            System.arraycopy(xt,1,x,0,32);
        }else if(xt.length<32){
            //如果计算结束后不足32字节则补齐
            System.arraycopy(xt,0,x,32-xt.length,xt.length);
        }else {
            x=xt;
        }
        //Y同X
        if(yt.length==33){
            System.arraycopy(yt,1,y,0,32);
        }else if(yt.length<32){
            System.arraycopy(yt,0,y,32-yt.length,yt.length);
        }else {
            y=yt;
        }
        //计算后的点组成公钥
        byte[] pubkey = new byte[64];
        System.arraycopy(x,0,pubkey,0,32);
        System.arraycopy(y,0,pubkey,32,32);
        System.out.println("总共进行了"+addCount+"次点加运算");
        System.out.println("总共进行了"+exGcdCount+"次模逆运算");
        addCount=exGcdCount=0;
        return new KeyPair(new ZyxxPublicKey(pubkey),new ZyxxPrivateKey(random));
    }


    /**
     * 二进制展开法倍点运算
     * 输人 :点 P,l比特的整数k=  ∈ (0,1)。
     * 输出:Q=[k]P。
     *   a) 置 Q=O;
     *   b) J从 l-1下 降到 0执 行 :
     *     b.1) Q=[2]Q;
     *     b.2) 若 kj=1 则 Q=Q+P;
     *   c) 输出 Q。
     * @param XG 基点X
     * @param YG 基点Y
     * @param k  k倍点
     * @param a  曲线参数a
     * @param p  有限域大小
     * @return 公钥坐标
     */
    private static byte[][] MultiplePointOperation(byte[] XG, byte[] YG, byte[] k,byte[] a,byte[] p) {
        byte[] XQ = new byte[33];
        byte[] YQ = new byte[33];
        String s = new BigInteger(k).toString(2);
        char[] chars = s.toCharArray();
        for (char c : chars) {
            //Q=[2]Q 先计算Q+Q
            byte[][] Q1bytes = PointAdditionOperation(XQ, YQ, XQ, YQ, a, p);
            XQ=Q1bytes[0];
            YQ=Q1bytes[1];
            if (c == '1') {
                //Q=Q+P 如果二进制展开后等于1则加P
                byte[][] Q2bytes = PointAdditionOperation(XQ, YQ, XG, YG, a, p);
                XQ=Q2bytes[0];
                YQ=Q2bytes[1];
            }
        }
        byte[][] Q = new byte[2][33];
        Q[0]=XQ;
        Q[1]=YQ;
        return Q;
    }


    /**
     *仿射点计算两点相加
     * @param X1 X1
     * @param Y1 Y1
     * @param X2 X2
     * @param Y2 Y2
     * @param a 曲线参数a
     * @param p 有限域大小
     * @return 结果点坐标
     */
    private static byte[][] PointAdditionOperation(byte[] X1, byte[] Y1, byte[] X2, byte[] Y2, byte[] a, byte[] p) {
        addCount++;
        BigInteger bigIntegerX1 = new BigInteger(X1);
        BigInteger bigIntegerY1 = new BigInteger(Y1);
        BigInteger bigIntegerX2 = new BigInteger(X2);
        BigInteger bigIntegerY2 = new BigInteger(Y2);
        BigInteger bigIntegera = new BigInteger(a);
        BigInteger bigIntegerp = new BigInteger(p);
        BigInteger bigIntegerzero = new BigInteger("0");
        //判断是否是0，0
        if(bigIntegerX1.equals(bigIntegerzero)&&bigIntegerY1.equals(bigIntegerzero)){
            byte[][] bytes = new byte[2][33];
            bytes[0] = X2;
            bytes[1] = Y2;
            return bytes;
        }
        if(bigIntegerX2.equals(bigIntegerzero)&&bigIntegerY2.equals(bigIntegerzero)){
            byte[][] bytes = new byte[2][33];
            bytes[0] = X1;
            bytes[1] = Y1;
            return bytes;
        }
        BigInteger k = null;
        //计算k
        if (!bigIntegerX1.equals(bigIntegerX2)) {
            // 所有中间运算都要进行模运算
            BigInteger tk1= bigIntegerY2.subtract(bigIntegerY1);
            tk1=mod(tk1,bigIntegerp);
            BigInteger tk2= bigIntegerX2.subtract(bigIntegerX1);
            tk2=mod(tk2,bigIntegerp);
            /**
             * 群里面没有除法运算
             * 除以一个数相当于乘以这个数的逆元
             * 逆元使用扩展欧几里得算法计算
             */
            BigInteger tk3= tk1.multiply(ex_gcd_ny(tk2,bigIntegerp));
            k=mod(tk3,bigIntegerp);
            //TODO P2 != -P1
        } else if (bigIntegerX1.equals(bigIntegerX2)) {
            // 所有中间运算都要进行模运算
            BigInteger tk4= bigIntegerX1.multiply(bigIntegerX1);
            tk4=mod(tk4,bigIntegerp);
            BigInteger tk5= new BigInteger("3").multiply(tk4);
            tk5=mod(tk5,bigIntegerp);
            BigInteger tk6= tk5.add(bigIntegera);
            tk6=mod(tk6,bigIntegerp);
            BigInteger tk7= new BigInteger("2").multiply(bigIntegerY1);
            tk7=mod(tk7,bigIntegerp);
            BigInteger tk8= tk6.multiply(ex_gcd_ny(tk7,bigIntegerp));
            k=mod(tk8,bigIntegerp);
        }
        BigInteger bigIntegerX3 = null;
        BigInteger bigIntegerY3 = null;
        if (k != null) {
            // 所有中间运算都要进行模运算（计算X）
            BigInteger tx1= k.multiply(k);
            tx1=mod(tx1,bigIntegerp);
            BigInteger tx2= tx1.subtract(bigIntegerX1);
            tx2=mod(tx2,bigIntegerp);
            BigInteger tx3= tx2.subtract(bigIntegerX2);
            bigIntegerX3=mod(tx3,bigIntegerp);
            //所有中间运算都要进行模运算（计算Y）
            BigInteger ty1= bigIntegerX1.subtract(bigIntegerX3);
            ty1=mod(ty1,bigIntegerp);
            BigInteger ty2= k.multiply(ty1);
            ty2=mod(ty2,bigIntegerp);
            BigInteger ty3= ty2.subtract(bigIntegerY1);
            bigIntegerY3=mod(ty3,bigIntegerp);
        }
        byte[][] bytes = new byte[2][33];
        if (bigIntegerX3 != null && bigIntegerY3 != null) {
            byte[] X3 = bigIntegerX3.toByteArray();
            byte[] Y3 = bigIntegerY3.toByteArray();
            bytes[0] = X3;
            bytes[1] = Y3;
        }
        return bytes;
    }

    private static BigInteger mod(BigInteger value,BigInteger mod){
        return value.mod(mod);
    }

    static BigInteger x,y;
    static BigInteger zero = new BigInteger("0");
    //扩展欧几里得算法求逆元
    private static BigInteger ex_gcd(BigInteger a,BigInteger b){
        if(b.equals(zero)){
            x=new BigInteger("1");
            y=zero;
            return a;
        }
        BigInteger[] bigIntegers = a.divideAndRemainder(b);
        BigInteger ans = ex_gcd(b,bigIntegers[1]);
        BigInteger tem = y;
        y=x.subtract(a.divide(b).multiply(y));
        x=tem;
        return ans;
    }
    //对计算结果进行处理
    private static BigInteger ex_gcd_ny(BigInteger a,BigInteger b){
        exGcdCount++;
        BigInteger d =ex_gcd(a,b);
        BigInteger t = b.divide(d);
        BigInteger[] bigIntegers = x.divideAndRemainder(t);
        x=(bigIntegers[1].add(t)).divideAndRemainder(t)[1];
        //部分计算结果是真实结果的相反数，此处只是简单的判断是否为负值
        //如果是则取相反数
        if(x.compareTo(zero) < 0){
            x=x.multiply(new BigInteger("-1"));
        }
        return x;
    }
    public static boolean checkPriKey(byte[] priKey,byte[] pubKey){
        //第一个字节补0
        //Biginteger会转换成有符号数造成精度丢失
        byte[] kt = new byte[33];
        System.arraycopy(priKey,0,kt,1,32);
        //倍点运算
        byte[][] bytes = MultiplePointOperation(XG, YG, kt, a, p);

        byte[] xt=bytes[0];
        byte[] yt=bytes[1];
        byte[] x=new byte[32],y=new byte[32],k=new byte[32];
        //计算结束后对计算结果进处理
        if(xt.length==33){
            //如果头部带有00则去出头部的00
            System.arraycopy(xt,1,x,0,32);
        }else if(xt.length<32){
            //如果计算结束后不足32字节则补齐
            System.arraycopy(xt,0,x,32-xt.length,xt.length);
        }else {
            x=xt;
        }
        //Y同X
        if(yt.length==33){
            System.arraycopy(yt,1,y,0,32);
        }else if(yt.length<32){
            System.arraycopy(yt,0,y,32-yt.length,yt.length);
        }else {
            y=yt;
        }
        //计算后的点组成公钥
        byte[] jsPubkey = new byte[64];
        System.arraycopy(x,0,jsPubkey,0,32);
        System.arraycopy(y,0,jsPubkey,32,32);
        if(new String(pubKey).equals(new String(jsPubkey))){
            return true;
        }else {
            return false;
        }
    }
}
